﻿using Modbus.Device;
using Modbus.Data;
using Serilog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using System.Diagnostics.Metrics;
using System.Net.Mail;
using System.Runtime.CompilerServices;

namespace Common.Communications
{
    /// <summary>
    /// modbus通信客户端服务
    /// </summary>
    public class ModbusCommunication
    {
        private IModbusMaster _master;
        private readonly string _ipAddress;
        private readonly int _port;
        private TcpClient client;
        private bool isConnected;

        private int reConnectNum = 0;

        public ModbusCommunication(string ipAddress, int port) 
        {
            _ipAddress = ipAddress;
            _port = port;
           
        }

        public async Task<bool> ConnectAsync()
        {
            try
            {
                if (client is not null)
                {
                    client.Close();
                }
                client = new TcpClient(_ipAddress, _port);
                if (client.Connected)
                {
                    _master = ModbusIpMaster.CreateIp(client);
                    reConnectNum = 0;
                    isConnected = true;
                    return true;
                }

                return false;
            }
            catch (Exception ex) 
            {
                Log.Error($"Modbus连接失败!{ex.Message}");
                return false;
            }
        }

        private bool ReConnect()
        {
            try
            {
                _master = null;
                if (client is not null)
                {
                    client.Close();
                }
                client = new TcpClient(_ipAddress, _port);
                if (client.Connected)
                {
                    _master = ModbusIpMaster.CreateIp(client);
                    reConnectNum = 0;
                    isConnected = true;
                    return true;
                }

                return false;
            }
            catch (Exception ex)
            {
                Log.Error($"Modbus重连失败!{ex.Message}");
                return false;
            }
        }
        private bool ReConnectProcess()
        {
            if (reConnectNum > 5)
            {
                return false;
            }
            while (!isConnected)
            {
                reConnectNum++;
                ReConnect();
            }
            return true;
        }

        #region 读
        /// <summary>
        /// 读取线圈 01H
        /// </summary>
        /// <param name="SlaveID"></param>
        /// <returns></returns>
        public List<bool> ReadCoil(byte SlaveID, ushort startAddress, ushort numberOfPoints)
        {
            List<bool> result = new List<bool>();
            if (isConnected)
            {
                try
                {
                    if (_master is null)
                    {
                        return null;
                    }
                    var infos = ReadCoilSub(SlaveID, startAddress, numberOfPoints);
                    if (infos is not null)
                    {
                        return infos;
                    }
                    isConnected = false;
                    if (!ReConnectProcess())
                    {
                        return null;
                    }
                    infos = ReadCoilSub(SlaveID, startAddress, numberOfPoints);
                    if (infos is null)
                    {
                        return null;
                    }
                    return infos;
                }
                catch (Exception ex)
                {
                    Log.Error($"读取线圈异常{ex.Message}");
                    isConnected = false;
                    if (!ReConnectProcess())
                    {
                        return null;
                    }
                    var infos = ReadCoilSub(SlaveID, startAddress, numberOfPoints);
                    if (infos is null)
                    {
                        return null;
                    }
                    return infos;
                }
            }
            return result;
        }

        private List<bool> ReadCoilSub(byte SlaveID, ushort startAddress, ushort numberOfPoints)
        {
            return _master.ReadCoils(SlaveID, startAddress, numberOfPoints)?.ToList();
        }
        /// <summary>
        /// 读取输入寄存器 04H
        /// </summary>
        /// <param name="SlaveID"></param>
        /// <param name="StartAddress"></param>
        /// <param name="NumOfPoint"></param>
        /// <returns></returns>
        public List<short> ReadInputRegisters(byte SlaveID, ushort StartAddress, ushort NumOfPoint)
        {
            if (isConnected)
            {
                try
                {
                    List<short> result = new List<short>();
                    if (_master is null)
                    {
                        return result;
                    }
                    ushort[] Uvalue = ReadInputRegistersSub(SlaveID, StartAddress, NumOfPoint);
                    if (Uvalue is not null)
                    {
                        return GetResultInfos(Uvalue);
                    }
                    isConnected = false;
                    if (!ReConnectProcess())
                    {
                        return null;
                    }
                    Uvalue = ReadInputRegistersSub(SlaveID, StartAddress, NumOfPoint);
                    if (Uvalue is null)
                    {
                        return null;
                    }
                    return GetResultInfos(Uvalue);
                }
                catch (Exception ex)
                {
                    Log.Error($"读取输入寄存器异常{ex.Message}");
                    isConnected = false;
                    if (!ReConnectProcess())
                    {
                        return null;
                    }
                    var Uvalue = ReadInputRegistersSub(SlaveID, StartAddress, NumOfPoint);
                    if (Uvalue is null)
                    {
                        return null;
                    }
                    return GetResultInfos(Uvalue);
                }
            }
            return null;
        }
        private ushort[] ReadInputRegistersSub(byte SlaveID, ushort StartAddress, ushort NumOfPoint)
        { 
            return _master.ReadInputRegisters(SlaveID, StartAddress, NumOfPoint);
        }
        private List<short> GetResultInfos(ushort[] Uvalue)
        {
            List<short> result = new List<short>();
            for (int i = 0; i < Uvalue.Length; i++)
            {
                if (Uvalue[i] > short.MaxValue)
                {
                    result.Add((short)(Uvalue[i] - 65536));
                }
                else
                {
                    result.Add((short)Uvalue[i]);
                }
            }
            return result;
        }
        /// <summary>
        /// 读取输入
        /// </summary>
        /// <param name="slaveAddress"></param>
        /// <param name="startAddress"></param>
        /// <param name="numberOfPoints"></param>
        /// <returns></returns>
        public List<bool> ReadInputs(byte SlaveID, ushort startAddress, ushort numberOfPoints)
        {
            if (isConnected)
            {
                try
                {
                    List<bool> bools = new List<bool>();
                    if (_master is null)
                    {
                        return bools;
                    }
                    var infos = ReadInputsSub(SlaveID, startAddress, numberOfPoints);
                    if (infos is not null)
                    {
                        return infos;
                    }
                    isConnected = false;
                    if (!ReConnectProcess())
                    {
                        return null;
                    }
                    infos = ReadInputsSub(SlaveID, startAddress, numberOfPoints);
                    if (infos is null)
                    {
                        return null;
                    }

                    return infos;
                }
                catch (Exception ex)
                {
                    Log.Error($"读取线圈异常{ex.Message}");
                    isConnected = false;
                    if (!ReConnectProcess())
                    {
                        return null;
                    }
                    var infos = ReadInputsSub(SlaveID, startAddress, numberOfPoints);
                    if (infos is null)
                    {
                        return null;
                    }

                    return infos;
                }
            }
            else
            {
                return null;
            }
        }
        private List<bool> ReadInputsSub(byte SlaveID, ushort startAddress, ushort numberOfPoints)
        {
           return _master.ReadInputs(SlaveID, startAddress, numberOfPoints)?.ToList();
        }
        /// <summary>
        /// 读取保存寄存器 fcode 03H
        /// </summary>
        /// <returns></returns>
        public List<short> ReadHoldingRegisters(byte SlaveID, ushort StartAddress, ushort NumOfPoint)
        {
            if (isConnected)
            {
                try
                {
                    List<short> result = new List<short>();
                    if (_master is null)
                    {
                        return result;
                    }
                    ushort[] Uvalue = ReadHoldingRegistersSub(SlaveID, StartAddress, NumOfPoint);
                    if (Uvalue is not null)
                    {
                        return GetResultInfos(Uvalue);
                    }
                    isConnected = false;
                    if (!ReConnectProcess())
                    {
                        return null;
                    }
                    Uvalue = ReadHoldingRegistersSub(SlaveID, StartAddress, NumOfPoint);
                    if (Uvalue is null)
                    {
                        return null;
                    }
                    return GetResultInfos(Uvalue);
                }
                catch (Exception ex)
                {
                    Log.Error($"读取保存寄存器异常{ex.Message}");
                    isConnected = false;
                    if (!ReConnectProcess())
                    {
                        return null;
                    }
                    var Uvalue = ReadHoldingRegistersSub(SlaveID, StartAddress, NumOfPoint);
                    if (Uvalue is null)
                    {
                        return null;
                    }
                    return GetResultInfos(Uvalue);
                }
            }
            else
            {
                return null;
            }
        }
        private ushort[] ReadHoldingRegistersSub(byte SlaveID, ushort StartAddress, ushort NumOfPoint)
        {
            return _master.ReadHoldingRegisters(SlaveID, StartAddress, NumOfPoint);
        }
        #endregion

        #region 写
        /// <summary>
        /// 写单个保存寄存器
        /// </summary>
        /// <param name="slaveAddress"></param>
        /// <param name="registerAddress"></param>
        /// <param name="value"></param>
        public bool WriteSingleRegister(byte slaveAddress, ushort registerAddress, short value) 
        {
            try
            {
                WriteSingleRegisterSub(slaveAddress, registerAddress, value);
                return true;
            }
            catch (Exception ex)
            {
                Log.Error($"写入单个保存寄存器异常{ex.Message}");
                isConnected = false;
                if (!ReConnectProcess())
                {
                    return false;
                }
                WriteSingleRegisterSub(slaveAddress, registerAddress, value);
                return true;
            }
        }
        private void WriteSingleRegisterSub(byte slaveAddress, ushort registerAddress, short value)
        {
            ushort Uvalue;
            if (value < 0)
            {
                Uvalue = (ushort)(ushort.MaxValue + value + 1);
            }
            else
            {
                Uvalue = (ushort)value;
            }
            _master.WriteSingleRegister(slaveAddress, registerAddress, Uvalue);
        }
        /// <summary>
        /// 写入单个线圈
        /// </summary>
        public bool WriteSingleCoil(byte slaveAddress, ushort coilAddress, bool value)
        {
            try
            {
                WriteSingleCoilSub(slaveAddress, coilAddress, value);
                return true;
            }
            catch(Exception ex) 
            {
                Log.Error($"写入单个线圈异常{ex.Message}");
                isConnected = false;
                if (!ReConnectProcess())
                {
                    return false;
                }
                WriteSingleCoilSub(slaveAddress, coilAddress, value);
                return true;
            }
        }

        private void WriteSingleCoilSub(byte slaveAddress, ushort coilAddress, bool value)
        {
            _master.WriteSingleCoil(slaveAddress, coilAddress, value);
        }
        #endregion


        private void Dispose()
        {
            if (client is not null)
            {
                client.Dispose();
                client.Close();
            }
            if (_master is not null)
            {
                _master.Dispose();
            }
        }
    }
}
